`timescale 1ns/100ps
`default_nettype none

`define FEOF 32'hFFFFFFFF

`define D2_FILE_NAME "motorcycle.d2"

module tb_SPLoad;

logic Clock_50;
logic resetn;

/*********************************************
 * Variables to drive SRAM emulator
 *********************************************/
wire [15:0] SRAM_data_io;
logic [15:0] SRAM_write_data, SRAM_read_data;
logic [17:0] SRAM_address_o;
logic [17:0] SRAM_address;
logic SRAM_we;
logic SRAM_UB_N;
logic SRAM_LB_N;
logic SRAM_WE_N;
logic SRAM_CE_N;
logic SRAM_OE_N;
logic SRAM_ready;

// The emulator for the external SRAM during simulation
tb_SRAM_Emulator SRAM_component (
    .Clock_50(Clock_50),
    .Resetn(resetn),
    
    .SRAM_data_io(SRAM_data_io),
    .SRAM_address(SRAM_address_o),
    .SRAM_UB_N(SRAM_UB_N),
    .SRAM_LB_N(SRAM_LB_N),
    .SRAM_WE_N(SRAM_WE_N),
    .SRAM_CE_N(SRAM_CE_N),
    .SRAM_OE_N(SRAM_OE_N)
);

// SRAM unit
SRAM_Controller SRAM_unit (
    .Clock_50(Clock_50),
    .Resetn(resetn),
    .SRAM_address(SRAM_address),
    .SRAM_write_data(SRAM_write_data),
    .SRAM_we_n(~SRAM_we),
    .SRAM_read_data(SRAM_read_data),        
    .SRAM_ready(SRAM_ready),
        
    // To the SRAM pins
    .SRAM_DATA_IO(SRAM_data_io),
    .SRAM_ADDRESS_O(SRAM_address_o),
    .SRAM_UB_N_O(SRAM_UB_N),
    .SRAM_LB_N_O(SRAM_LB_N),
    .SRAM_WE_N_O(SRAM_WE_N),
    .SRAM_CE_N_O(SRAM_CE_N),
    .SRAM_OE_N_O(SRAM_OE_N)
);

/**********************************************
 * Signals for Dual Port RAM
 **********************************************/
logic [6:0] DP_Addr0, DP_Addr1;
logic [31:0] DP_Read0, DP_Read1;
logic [31:0] DP_Write0, DP_Write1;
logic DP_we0, DP_we1;

// We are only testing one control set of dual port RAM
// Initialize unused to 0s
assign DP_Addr1 = 7'b0;
assign DP_Write1 = 32'b0;
assign DP_we1 = 1'b0;

DP_RAM0 dp_ram_inst0 (
    .address_a(DP_Addr0),
    .address_b(DP_Addr1),
    .clock(Clock_50),
    .data_a(DP_Write0),
    .data_b(DP_Write1),
    .wren_a(DP_we0),
    .wren_b(DP_we1),
    .q_a(DP_Read0),
    .q_b(DP_Read1)
);

/**********************************************
 * Signals for SPLoad
 **********************************************/
logic start, finish, clearResult, preIDCTYDone;
logic [17:0] SRAM_BaseAddr;
assign SRAM_BaseAddr = 18'b0;
assign preIDCTYDone = 1'b0;
SPLoad uut (
    .CLOCK_50_I(Clock_50),
    .resetn(resetn),
    .clearResult(clearResult),
    .preIDCTYDone_in(preIDCTYDone),
    .DP_Read(DP_Read0),
    .SRAM_Read(SRAM_read_data),
    .SRAM_BaseAddr(SRAM_BaseAddr),
    .start(start),
    
    .DP_Addr(DP_Addr0),
    .DP_Write(DP_Write0),
    .DP_we(DP_we0),
    .SRAM_Addr(SRAM_address),
    .finish(finish)
);

// Generate a 50 MHz clock
always begin
    # 10;
    Clock_50 = ~Clock_50;
end

// Task for generating master reset
task master_reset;
begin
    wait (Clock_50 !== 1'bx);
    @ (posedge Clock_50);
    $write("Applying global reset...\n\n");
    resetn = 1'b0;
    // Activate reset for 2 clock cycles
    @ (posedge Clock_50);
    @ (posedge Clock_50);   
    resetn = 1'b1;    
    $write("Removing global reset...\n\n"); 
end
endtask

// Task for filling the SRAM with d2 file
task fill_SRAM;
    integer d2file, file_data, temp, i, new_line_count;
    logic [15:0] buffer;
begin
    $write("Opening file \"%s\" for initializing SRAM\n\n", `D2_FILE_NAME);
    d2file = $fopen(`D2_FILE_NAME, "rb");
    file_data = $fgetc(d2file);
    i = 0;
    while (file_data != `FEOF) begin
        buffer[15:8] = file_data & 8'hFF;
        file_data = $fgetc(d2file);          
        buffer[7:0] = file_data & 8'hFF;
        SRAM_component.SRAM_data[i] = buffer;
        //$write("%x\n", buffer);
        i++;

        file_data = $fgetc(d2file);
    end

    $fclose(d2file);

    $write("Finish initializing SRAM\n\n");
end
endtask

// Initialize signals
initial begin
    // This is for setting the time format
    $timeformat(-3, 2, " ms", 10);
    
    $write("Simulation started at %t\n\n", $realtime);
    Clock_50 = 1'b0;
    resetn = 1'b1;
    start = 1'b0;
    clearResult = 1'b0;
    SRAM_we = 1'b0;

    // Apply master reset
    master_reset;
    @ (posedge Clock_50);

    fill_SRAM;
    $write("SRAM is now filled at %t\n\n", $realtime);
    @ (posedge Clock_50);   
    @ (posedge Clock_50);   
    @ (posedge Clock_50);   

    @ (posedge Clock_50);   
    start = 1'b1;
    wait (uut.state != 0);
    $write("Starting SPLoad at %t\n\n", $realtime);
    start = 1'b0;

    wait(uut.finish == 1);
    $write("SPLoad finished at %t\n\n", $realtime);

    @ (posedge Clock_50);   
    @ (posedge Clock_50);   
    // Test clearResult
    clearResult = 1'b1;
    @ (posedge Clock_50);   
    clearResult = 1'b0;
    @ (posedge Clock_50);   
    @ (posedge Clock_50);   

    $stop;
end

endmodule
